/*
 LCD - 12, 11, 4, 5, 6, 7
 PH - 18, 19 (Serial1)
 ORP - 16, 17 (Serial2)
 RTC - 0, 1
 LED Beleuchtung - 10
 Relais 34, 35, 56, 37
 Buzzer - 9
 NewEtherShield - 50, 51, 52, 53, 2 (INT), 4 (SD_CS), Reset 1, VCC 3, GND 4, 5
 */
#include <LiquidCrystal.h>
#include <WProgram.h>
#include <Wire.h>
#include <DS1307new.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <I2C_eeprom.h>
#include <PString.h>
#include <avr/eeprom.h>
#include <Streaming.h>

#define DEBUG
//#define DEBUG_EEPROM
#define ledTestPin 13
#define ledLightPin 10
#define tempSensorPin 45
#define relaisPin1 34
#define relaisPin2 35
#define relaisPin3 36
#define relaisPin4 37
#define buzzerPin 9

// PH
const float pHMax = 6.90;
const float pHMin = 6.50;
const float pHMaxAlarm = 8.5;
const float pHMinAlarm = 5.5;
float pHCalibrationValue = 0.0;
float pHValue = 0.0;
float calPHValue = 0.0;
const int eeAddrPHCal = 5;
// ORP
const int eeAddrORPCal = 10;
// LED
byte minLuminousIntensity = 1;
const float ledOn1 = 7.33; // time in decimal format. z.B 7:20 = 7.33
const float ledOff1 = 11;
const float ledOn2 = 16.50;
const float ledOff2 = 20.33;
// clock variables
uint16_t startAddr = 0x0000;            // Start address to store in the NV-RAM
uint16_t lastAddr;                      // new address for storing in NV-RAM
uint16_t TimeIsSet = 0xaa55;            // Helper that time must not set again
// initialize the library with the numbers of the interface pins
LiquidCrystal LCD(12, 11, 4, 5, 6, 7);
// PH and ORP stamp
int orpValue = 0;
// global variables
byte luminousIntensity = minLuminousIntensity;
byte curHour;
byte oldHour;
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(tempSensorPin);
// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// arrays to hold device address
DeviceAddress insideThermometer;
float tempC;
// EEPROM
I2C_eeprom EE(0x50);
const unsigned long EEPROM_SIZE = 65536;      // 24LC512 64K bytes
unsigned long eeAddr = 0;
const int eeAddrCounter = 0;
const byte blockLen = 13;

void setup() {
  pinMode(ledTestPin, OUTPUT);  //we'll use the debug LED to output a heartbeat
  //Buzzer
  pinMode(buzzerPin, OUTPUT);
  // Relais
  pinMode(relaisPin1, OUTPUT);
  pinMode(relaisPin2, OUTPUT);
  pinMode(relaisPin3, OUTPUT);
  pinMode(relaisPin4, OUTPUT);
  // Relais default value
  digitalWrite(relaisPin1, HIGH);
  digitalWrite(relaisPin2, HIGH);
  digitalWrite(relaisPin3, HIGH);
  digitalWrite(relaisPin4, HIGH);
  // LED Helligkeitswert auf 0
  analogWrite(ledLightPin, 0); // setzt den Helligkeitswert auf i
  // set up the LCD's number of columns and rows:
  LCD.begin(20, 4);
  // start clock
  RTC.getRAM(54, (uint8_t *)&TimeIsSet, sizeof(uint16_t));
  if (TimeIsSet != 0xaa55)
  {
    Serial.println("INFO:Time is not defined. Set time now.");
    RTC.stopClock();
    RTC.fillByYMD(2011, 7, 14);
    RTC.fillByHMS(19, 55, 00);
    RTC.setTime();
    RTC.startClock();
    TimeIsSet = 0xaa55;
    RTC.setRAM(54, (uint8_t *)&TimeIsSet, sizeof(uint16_t));
  }
  // Control Register for SQW pin which can be used as an interrupt.
  RTC.ctrl = 0x00; // 0x00=disable SQW pin, 0x10=1Hz, 0x11=4096Hz, 0x12=8192Hz, 0x13=32768Hz
  RTC.setCTRL();
  Serial.begin(9600);
  // pH stamp
  Serial1.begin(38400);
  Serial1.print("L0");
  Serial1.print(13, BYTE);
  pHCalibrationValue = eepromReadFloat(eeAddrPHCal);
  // ORP stamp
  Serial2.begin(38400);
  Serial2.print("L0");
  Serial2.print(13, BYTE);
  // Start up the library
  sensors.begin();
  if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0");
  // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
  sensors.setResolution(insideThermometer, 9);
  // read start address of datalogger from ATMEL EEPROM
  eeAddr = eepromReadFloat(eeAddrCounter);
}

void loop() {
  blinkTestLed();

  readUserCommands();
  sensors.requestTemperatures(); // Send the command to get temperatures
  tempC = sensors.getTempC(insideThermometer);
  if(tempC == -127)
  {
    // reset temp.sensor
    // Start up the library
    sensors.begin();
    if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); 
    // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
    sensors.setResolution(insideThermometer, 9);
    sensors.requestTemperatures(); // Send the command to get temperatures
    tempC = sensors.getTempC(insideThermometer);
  }
  printDate();
  //printPH();
  //printORP();
  printLightPercent(luminousIntensity);
  printTemp();
  saveSensorsValueToEEPROM();
}

void blinkTestLed()
{
  if(digitalRead(ledTestPin))
    digitalWrite(ledTestPin, LOW); 
  else
    digitalWrite(ledTestPin, HIGH); 
}

void printORP()
{
  // ph stamp a new command
  Serial2.print("r");  // send "r" to read value in XXX.XX mV
  Serial2.print(13,BYTE); // ende
  delay(550);

  LCD.setCursor(10, 1);
  LCD.print("ORP ");
  byte holding=Serial2.available();            //lets read how many bytes have been received
#ifdef DEBUG
  Serial.print(holding, DEC);
  Serial.println(" byte received from ORP stamp");
#endif

  if(holding > 4 && holding <=12) {  //if we see the more than three bytes have been received by the Arduino
    char data[12];          //this is where the data from the stamp is stored. if no pH probe is connected, the message "check probe" is transmitted.
    for(byte i=0; i <= holding-1;i++) {                     //we make a loop that will read each byte we received
      data[i]= char(Serial2.read());     //and load that byte into the stamp_data array
    }
#ifdef DEBUG
    Serial.print("ORP=");
    for(byte i=0; i <= holding-1;i++){   //we now loop through the array
      Serial.print(data[i]);             //printing each byte we received  through the hardware UART
    }
    Serial.println("");
#endif	
    if (atoi(data) != 0) {
      orpValue = atof(data);
    }
    else 
      orpValue = 0;

    LCD.print(orpValue, DEC);
    LCD.print(" ");
  }
  else if(holding == 0)
  {
    Serial.println("Error: No data from ORP stamp");
  }
  else if(holding > 12)
  {
    Serial.println("Error: Too many data from ORP stamp. Max 12 is allowed.");
  }
  Serial2.flush();
}

void printPH()
{
  // ph stamp a new command
  if(tempC > 0)
  {
    // temperatur sensor nicht angeschlossen
    Serial1.print(tempC, DEC);  // send temperatur or "r". nicht beides!
    Serial1.print(13, BYTE); // ende
  }
  else
  {
    Serial1.print("r");  // send temperatur or "r". nicht beides!
    Serial1.print(13, BYTE); // ende
  }
  delay(550);

  LCD.setCursor(0, 1);
  LCD.print("PH ");
  byte holding=Serial1.available();            //lets read how many bytes have been received
#ifdef DEBUG
  Serial << _DEC(holding) << " byte received from PH stamp" << endl;
#endif

  if(holding > 4 && holding <=12) {  //if we see the more than three bytes have been received by the Arduino
    char data[12];          //this is where the data from the stamp is stored. if no pH probe is connected, the message "check probe" is transmitted.
    for(byte i=0; i <= holding-1;i++) {  //we make a loop that will read each byte we received
      data[i]= char(Serial1.read());     //and load that byte into the stamp_data array
    }
#ifdef DEBUG
    Serial.print("PH=");
    for(byte i=0; i <= holding-1;i++) {  //we now loop through the array
      Serial.print(data[i]);             //printing each byte we received  through the hardware UART
    }
    Serial.println(""); 
#endif
    if (atoi(data) != 0) {
      pHValue = atof(data);
    }
    else 
      pHValue = 0;

    if(pHValue > 0) calPHValue = pHValue + pHCalibrationValue;
    LCD << _FLOAT(calPHValue, 2) << " ";
  }
  else if(holding == 0)
  {
    Serial.println("Error: No data from PH probe");
  }
  else if(holding > 12)
  {
    Serial.println("Error: Too many data from PH probe. Max 12 is allowed.");
  }
  if(pHValue > 0.0 )
  {
    // Alarm beim PH Sensor
    if(calPHValue <= pHMinAlarm || calPHValue >= pHMaxAlarm)
    {
      Alarm("Check PH Value");
    }
    else
      ClearAlarmMessage();

    // CO2 einschalten oder ausschalten
    if(((int)pHValue*100) > ((int)pHMax*100)) {
#ifdef DEBUG
      Serial << "PH >= " << pHMax << endl;
#endif
      digitalWrite(relaisPin1, LOW); // LOW ist einschlten!
    }
    else if(((int)pHValue*100) < ((int)pHMin*100)) {
#ifdef DEBUG
      Serial << "PH <= " << pHMin << endl;
#endif
      digitalWrite(relaisPin1, HIGH); // HIGH ist ausschlten!
    }
    else
      digitalWrite(relaisPin1, HIGH);
  }
  Serial1.flush();
}

void printDate()
{
  LCD.setCursor(0, 0);
  RTC.getTime();
  char text[20];
  sprintf(text, "Datum %02d-%02d %02d:%02d:%02d", RTC.day, RTC.month, RTC.hour, RTC.minute, RTC.second);
  LCD.print(text);
}

void printLightPercent(byte value)
{
  RTC.getTime();
  // led on time
  float time = RTC.hour + ((float)RTC.minute / (float)60) + ((float)RTC.second / (float)3600);
  if((time >= ledOn1 && time <= ledOff1) || (time >= ledOn2 && time <= ledOff2))
  {
    if(luminousIntensity < 255) luminousIntensity += 1;
  }
  else
  {
    if(luminousIntensity > minLuminousIntensity) luminousIntensity -= 1;
  }
#ifdef DEBUG
  Serial << "Luminous Intensity = " << _DEC(luminousIntensity) << endl;
#endif
  analogWrite(ledLightPin, 255-luminousIntensity); // invert

  LCD.setCursor(0, 2);
  char buffer[10];
  byte percent = byte(double(value)/255*100+0.1);
  sprintf(buffer, "Licht %3d%%", percent);
  LCD.print(buffer);  
#ifdef DEBUG
  Serial << "Licht = " << _DEC(percent) << "%" << endl;
#endif
}

void printTemp()
{
  LCD.setCursor(12, 2);
  LCD << _FLOAT(tempC, 1) << _BYTE(223) << "C";
#ifdef DEBUG
  Serial << "Temp = " << _FLOAT(tempC, 1) << "C" << endl;
#endif  
}

// example: lcdPrintFloat( 3.1415, 2); // prints 3.14 (two decimal places)
void lcdPrintFloat( float val, byte precision) 
{
  char buffer[7];
  PString text(buffer, sizeof(buffer));

  if(val < 0.0) {
    text.print('-');
    val = -val;
  }
  text.print ((long)val, DEC);  //prints the integral part
  if( precision > 0) {
    text.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
      mult *=10;
    if(val >= 0)
      frac = (val - int(val)) * mult;
    else
      frac = (int(val)- val) * mult;
    unsigned long frac1 = frac;
    while(frac1 /= 10)
      padding--;
    while( padding--)
      text.print("0");
    text.print(frac, DEC);
  }
  text.print((char)223);
  text.print("C");
  LCD.print(text);
}

/*
  Audible alarm.
 */
void Alarm(String text)
{
  Buzzer(buzzerPin, 1500, 100); // buzz the buzzer on pin 9 at 1500Hz for 100 milliseconds
  LCD.setCursor(0, 3);
  LCD.print(text);  
}

void ClearAlarmMessage()
{
  LCD.setCursor(0, 3);
  LCD.print("                    "); 
}

/*
  Buzzer.
 */
void Buzzer(int targetPin, long frequency, long length) {
  long delayValue = 1000000/frequency/2; // calculate the delay value between transitions
  // 1 second's worth of microseconds, divided by the frequency, then split in half since
  // there are two phases to each cycle
  long numCycles = frequency * length/ 1000; // calculate the number of cycles for proper timing
  // multiply frequency, which is really cycles per second, by the number of seconds to 
  // get the total number of cycles to produce
  for (long i=0; i < numCycles; i++){ // for the calculated length of time...
    digitalWrite(targetPin,HIGH); // write the buzzer pin high to push out the diaphram
    delayMicroseconds(delayValue); // wait for the calculated delay value
    digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphram
    delayMicroseconds(delayValue); // wait againf or the calculated delay value
  }
}

/*
 Save time, temperatur and PH Value to EEPROM each hour.
 */
void saveSensorsValueToEEPROM()
{
  RTC.getTime();
  if(RTC.hour != oldHour)
  {
    // save to eeprom
    char buffer[blockLen];
    PString text(buffer, sizeof(buffer));
    // add time
    if(RTC.day < 10) text.print("0");
    text.print(RTC.day, DEC);
    if (RTC.hour < 10) text.print("0");
    text.print(RTC.hour, DEC);
    // add temperatur
    if(tempC == -127)
      text.print("0.00");
    else
      text << _FLOAT(tempC, 1);
    // add ph
    text.print(calPHValue);
    // write data to eeprom
    EE.writeBlock(eeAddr, (uint8_t*) buffer, blockLen);
    eeAddr += blockLen;
    // reset addr if max erreicht.
    if(eeAddr+blockLen > EEPROM_SIZE) eeAddr = 0;
    // save start address to ATMEL EEPROM
    eepromWriteFloat(eeAddrCounter, eeAddr);
#ifdef DEBUG_EEPROM
    dumpEEPROM(eeAddr - blockLen, blockLen); // print last entry  
#endif
    oldHour = RTC.hour;
  }
}

/*
  Print data from EEPROM to serial monitor.
 addr - start eeprom adresse.
 length - data block length.
 */
void dumpEEPROM(unsigned int addr, unsigned long len)
{
#ifdef DEBUG_EEPROM
  Serial << "EEPROM Start : " << _DEC(addr) << endl;
  Serial << "EEPROM length: " << _DEC(len) << endl;
#endif
  byte b = EE.readByte(addr);
  for (unsigned long i = 0; i < len; i += blockLen) {
    char buffer[55];
    PString text(buffer, sizeof(buffer));

    Serial.print(addr);
    Serial.print(":\t");

    for (int j = 0; j < blockLen; j++) {
      if(j == 0) text.print("Date ");
      if(j == 2) text.print("-07-2011 ");
      if(j == 4) text.print(".00 Temp=");
      if(j == 8) text.print("C PH=");
      text.print(b);
      b = EE.readByte(++addr);
    }
    Serial.println(text);
  }
}

void caribratePHProbe()
{
  pHCalibrationValue = pHCalibrationValue + (7 - pHValue);
  Serial << "PH difference = " << _FLOAT(7 - pHValue, 2);
  eepromWriteFloat(pHCalibrationValue, eeAddrPHCal);
}

float eepromReadFloat(int address) {
  float out;
  eeprom_read_block((void *) &out, (unsigned char *) address ,4 );
  return out;
}

void eepromWriteFloat(float value, int address) {
  eeprom_write_block((void *) &value, (unsigned char *) address ,4);
}

void readUserCommands()
{
  if (Serial.available() > 0) {
    int inByte = Serial.read();

    switch (inByte) {
    case 'h':
      Serial.println("h - Help");
      Serial.println("s - Show sensors values");
      Serial.println("d - Dump eeprom");
      Serial.println("p - Calibrate PH probe. Probe in PH 7.0 water, wait and press 'p'.");
      break;
    case 's':
      dumpEEPROM(0, eeAddr);
      break;
    case 'd':
      dumpEEPROM(0, EEPROM_SIZE);
      break;
    case 'p':
      caribratePHProbe();
      break;
    default:
      break;
    }
  }
}



